package goTeam.up.cs301.go;

import goTeam.up.cs301.animation.AnimationSurface;
import goTeam.up.cs301.animation.Animator;
import goTeam.up.cs301.game.GameMainActivity;
import goTeam.up.cs301.game.R;
import goTeam.up.cs301.game.infoMsg.GameInfo;
import goTeam.up.cs301.game.infoMsg.IllegalMoveInfo;
import goTeam.up.cs301.game.infoMsg.NotYourTurnInfo;
import goTeam.up.cs301.game.util.MessageBox;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

/**
 * A GUI that allows a human to play go. Moves are made by clicking regions on a
 * canvas
 * 
 * @author Steven R. Vegdahl
 * @author Ryan Gasik
 * @author Matthew Young
 * @author Casey Sigelmann
 * @version November 2013
 */
public class GoHumanPlayer1 extends GoHumanPlayer implements Animator {

	public final static int NUM_LINES = 19; // The number of lines to draw in, should normally be 19
	// each direction. Used to define
	// game size
	private final static float LINE_DRAW_CONSTANT = 0.95f; // How far to go
	// while drawing
	// lines
	private final static float LINE_WIDTH = 4; // width of a go line
	private final static double ONE_THIRD = 1.0 / 3.0; // Precompute this
	private double spacing = (float) 1.0 / (NUM_LINES + 1);

	/*
	 * Instance variables
	 */
	private int currentWidth = -1;
	private int currentHeight = -1;

	// Store the location of the temporary stone.
	private int tmpStoneX, tmpStoneY;
	private boolean tmpStoneSet;

	// Store the last known opponent move position
	private int opponentLastRow, opponentLastCol;

	// Proportion of the grid
	private boolean isDrawing;

	private boolean didWeJustPass;

	// the game's state
	protected GoState state;

	// the last state, used so the animator can tell what the last move made was
	protected GoState lastState;

	// the views to display the number of black and white stones and current
	// player
	private TextView blackDisplay;
	private TextView whiteDisplay;
	private TextView playerDisplay;

	// the buttons for committing moves and passing
	private Button passButton;
	private Button commitButton;

	// let us know if game is over
	private boolean gameIsOver;
	
	// the current activity
	private Activity myActivity;

	// our animation surface. (We're not actually doing moving animation, but
	// this
	// surface is a convenient way to draw our image.)
	private AnimationSurface surface;

	/**
	 * constructor
	 * 
	 * @param name
	 *            the player's name
	 */
	public GoHumanPlayer1(String name) {
		super(name);
	}

	/**
	 * Callback method, called when player gets a message
	 * 
	 * @param info
	 *            the message
	 */
	@Override
	public void receiveInfo(GameInfo info) {
		if (info instanceof IllegalMoveInfo || info instanceof NotYourTurnInfo) {
			// if the move was out of turn or otherwise illegal, flash the
			// screen
			flashBoard();
			if (info instanceof IllegalMoveInfo)
				tmpStoneSet = true;
		} else if (!(info instanceof GoState))
			// if we do not have a GoState, ignore
			return;
		else {
			// update our 'state' variable with the new state and our laststate
			// with the (now dated) state
			this.lastState = this.state;
			this.state = (GoState) info;
			Log.i("human player", "receiving");

			// Remove the temp stone if the opponent played there
			if (state.getPiece(tmpStoneY, tmpStoneX) != ' ') {
				tmpStoneSet = false;
			}

			// Check to see if the opponent passed
			if (state.wasLastMoveAPass() && state.getWhoseMove() == playerNum
					&& !state.gameOver && !didWeJustPass) {
				MessageBox.popUpMessage((state.getWhoseMove() == 0 ? "White"
						: "Black") + " just passed.", myActivity);
			} else {

			}

			// Log the fact that we just passed, if applicable
			if (state.wasLastMoveAPass() && state.getWhoseMove() != playerNum) {
				didWeJustPass = true;
			} else {
				didWeJustPass = false;
			}
		}

		// display score
		blackDisplay.setText("" + state.getScore('b'));
		whiteDisplay.setText("" + state.getScore('w'));
		if (state.getWhoseMove() == 0) {
			playerDisplay.setText("Black");
		} else {
			playerDisplay.setText("White");
		}

	}

	/**
	 * Flash the game board to scold the user
	 */
	private void flashBoard() {
		surface.flash(Color.RED, 50);
	}

	/**
	 * sets the current player as the activity's GUI
	 */
	public void setAsGui(GameMainActivity activity) {

		// remember our activity
		myActivity = activity;

		// Load the layout resource for the new configuration
		activity.setContentView(R.layout.go_human_player1and2);

		// set the animator (us) for the animation surface
		surface = (AnimationSurface) myActivity.findViewById(R.id.padding8Bot);
		surface.setAnimator(this);

		// if we have state, "simulate" that it just came from the game, and
		// hence draw it on the GUI
		if (state != null) {
			receiveInfo(state);
		}
	}

	/**
	 * returns the GUI's top view
	 * 
	 * @return the GUI's top view
	 */
	@Override
	public View getTopView() {
		return myActivity.findViewById(R.id.top_gui_layout);
	}

	/**
	 * @return the time interval for the animation
	 */
	public int interval() {
		// 17 milliseconds, or 58 times per second
		return 17;
	}

	/**
	 * @return the animation's background color and the color of white stones
	 */
	public int getWhite() {
		return Color.WHITE;
	}

	/**
	 * @return whether the animation should be pause
	 */
	public boolean doPause() {
		return gameIsOver;
	}

	/**
	 * @return whether the animation should quit
	 */
	public boolean doQuit() {
		// never tell the animation to quit
		return false;
	}

	/**
	 * perform any initialization that needs to be done after the player knows
	 * what their game-position and opponents' names are.
	 */
	protected void initAfterReady() {
		myActivity.setTitle("Go: " + allPlayerNames[0] + " vs. "
				+ allPlayerNames[1]);

		// save reference to score text views
		blackDisplay = (TextView) myActivity
				.findViewById(R.id.blackPlayerValue);
		whiteDisplay = (TextView) myActivity
				.findViewById(R.id.whitePlayerValue);
		playerDisplay = (TextView) myActivity
				.findViewById(R.id.currentPlayerValue);

		// save reference to buttons
		passButton = (Button) myActivity.findViewById(R.id.passButton);
		commitButton = (Button) myActivity.findViewById(R.id.commitButton);
	}

	/**
	 * callback method, called whenever it's time to draw the next animation
	 * frame
	 * 
	 * @param g
	 *            the canvas to draw on
	 */
	public void tick(Canvas g) {
		currentWidth = g.getWidth();
		currentHeight = g.getHeight();

		if (isDrawing)
			return; // Drop this frame
		isDrawing = true;

		drawLines(g);

		// if we don't have any state, there's nothing more to draw, so return
		if (null == state) {
			isDrawing = false;
			return;
		}

		// for each square that has a stone, draw it on the appropriate
		// place on the canvas
		for (int row = 0; row < NUM_LINES; ++row) {
			for (int col = 0; col < NUM_LINES; ++col) {
				char result = state.getPiece(row, col); // get piece
				drawStone(g, result, col, row);
			}
		}

		if (tmpStoneSet) {
			// Draw the temp stone
			drawStone(g, this.playerNum == 0 ? 'b' : 'w', tmpStoneX, tmpStoneY);
		}

		// Highlight the last move made so the player can see what the opponent
		// did
		if (null != lastState && !lastState.equals(state)) {
			findAndHighlightNewestMove(g);
		}

		isDrawing = false;

		// If game over, disable buttons
		if (state.gameOver) {
			passButton.setEnabled(false);
			commitButton.setEnabled(false);
		}

	}

	/*
	 * Finds the difference between the current game state and the last one and
	 * highlights it
	 */
	private void findAndHighlightNewestMove(Canvas g) {

		// Find the new stone
		int row = 0;
		int col = 0;
		OUTER: for (; col < NUM_LINES; ++col) {
			for (row = 0; row < NUM_LINES; ++row) {

				char curPiece = state.getPiece(row, col);
				char lastPiece = lastState.getPiece(row, col);
				if (curPiece != lastPiece) {
					break OUTER;
				}

			}
		}


		if (row == NUM_LINES) row = opponentLastRow;
		if (col == NUM_LINES) col = opponentLastCol;

		opponentLastRow = row;
		opponentLastCol = col;

		char sym = state.getPiece(row, col);
		if (' ' == sym || (playerNum == 0 ? 'b' : 'w') == sym) return;

		Paint body = new Paint(Paint.ANTI_ALIAS_FLAG);
		body.setColor('b' == sym ? getBlack() : getWhite());

		Paint outline = new Paint(Paint.ANTI_ALIAS_FLAG);
		outline.setColor(Color.RED);

		// compute the pixel-location
		int xLoc = h(spacing * (1 + col));
		int yLoc = v(spacing * (1 + row));

		float physicalDrawSizeBase = currentWidth < currentHeight ? h(spacing
				* ONE_THIRD) : v(spacing * ONE_THIRD);

		// Draw the stone with a red outline
		g.drawCircle(xLoc, yLoc, physicalDrawSizeBase + 1.5f * LINE_WIDTH,
				outline);
		g.drawCircle(xLoc, yLoc, physicalDrawSizeBase, body);

	}

	/**
	 * Draw lines on the canvas
	 * 
	 * @param g
	 *            The canvas to draw on
	 */
	private void drawLines(Canvas g) {
		// Prep the paint for line drawing
		Paint p = new Paint();
		p.setStrokeWidth(LINE_WIDTH);
		p.setStyle(Paint.Style.STROKE);
		p.setColor(getBlack());

		float drawPos = 0;
		// Draw horizontal lines until we reach the end of the canvas
		while (drawPos < LINE_DRAW_CONSTANT) {
			drawPos += spacing;
			g.drawLine(h(0 + spacing), v(drawPos), h(1 - spacing), v(drawPos),
					p);
		}

		drawPos = 0;
		// Draw vertical lines until we reach the end of the canvas
		while (drawPos < LINE_DRAW_CONSTANT) {
			drawPos += spacing;
			g.drawLine(h(drawPos), v(0 + spacing), h(drawPos), v(1 - spacing),
					p);
		}
	}

	/**
	 * @return the color to paint the lines on the board and black stones
	 */
	public int getBlack() {
		return Color.BLACK;
	}

	/**
	 * callback method when the screen it touched. We're looking for a screen
	 * touch (which we'll detect on the "up" movement" onto a go intersection)
	 * 
	 * @param event
	 *            the motion event that was detected
	 */
	public void onTouch(MotionEvent event) {

		// ignore if not an "up" event
		if (event.getAction() != MotionEvent.ACTION_UP)
			return;

		// get the x and y coordinates of the touch-location;
		// convert them to square coordinates (where both
		// values are in the range 0..NUM_LINES)
		int x = (int) event.getX();
		int y = (int) event.getY();
		Point p = mapPixelToIntersection(x, y);

		GoState theoreticalState = new GoState(state);
		boolean isValidSpot = testMove(theoreticalState, p.x, p.y);
		if (!isValidSpot) {
			return;
		}

		tmpStoneX = p.x;
		tmpStoneY = p.y;
		tmpStoneSet = true;

	}

	/*
	 * Checks to see if we should allow the temp stone to be placed here.
	 * 
	 * Just checks for overlap and out of bounds. Leave it up to the game to
	 * decide regarding the rules of Go.
	 */
	private boolean testMove(GoState theoreticalState, int x, int y) {
		// if that space is not blank, indicate an illegal move
		if (theoreticalState.getPiece(y, x) != ' ') {
			return false;
		}

		// place the player's piece on the selected square
		theoreticalState.setPiece(y, x, this.playerNum == 0 ? 'b' : 'w');

		// capture pieces
		theoreticalState.removeCapturedStones(this.playerNum == 0 ? 'b' : 'w');

		// Check liberties
		if (!theoreticalState.hasLiberties(y, x, this.playerNum == 0 ? 'b'
				: 'w')) {
			return false;
		}
		return true;
	}

	/**
	 * Commits the temp stone by sending it to the game.
	 */
	public void makeStoneMove() {
		if (!tmpStoneSet) {
			flashBoard();
			return;
		}
		// Create and send an action to the game
		GoMoveAction action = new GoMoveAction(this, tmpStoneY, tmpStoneX,
				false);
		Log.i("makeStoneMove", "Human player sending GoMA ...");
		game.sendAction(action);
		tmpStoneSet = false;
	}

	/**
	 * Draw a stone (black or white) on the canvas in a particular location.
	 * Regardless of color, the stone is always outlined in black.
	 * 
	 * @param g
	 *            the graphics object on which to draw
	 * @param sym
	 *            the symbol to draw (b or w)
	 * @param col
	 *            the column number of the square on which to draw (0 ..
	 *            NUM_LINES)
	 * @param col
	 *            the row number of the square on which to draw (0 .. NUM_LINES)
	 */
	protected void drawStone(Canvas g, char sym, int col, int row) {

		// Check for blank space
		if ('b' != sym && 'w' != sym)
			return;

		Paint body = new Paint(Paint.ANTI_ALIAS_FLAG);
		body.setColor('b' == sym ? getBlack() : getWhite());

		Paint outline = new Paint(Paint.ANTI_ALIAS_FLAG);
		outline.setColor(getBlack());

		// compute the pixel-location
		int xLoc = h(spacing * (1 + col));
		int yLoc = v(spacing * (1 + row));

		float physicalDrawSizeBase = currentWidth < currentHeight ? h(spacing
				* ONE_THIRD) : v(spacing * ONE_THIRD);

		// Draw the stone with a pleasant outline
		g.drawCircle(xLoc, yLoc, physicalDrawSizeBase + LINE_WIDTH, outline);
		g.drawCircle(xLoc, yLoc, physicalDrawSizeBase, body);

	}

	/**
	 * maps a point from the canvas' pixel coordinates to "intersection"
	 * coordinates
	 * 
	 * @precondition the coordinates are on the canvas
	 * 
	 * @param x
	 *            the x pixel-coordinate
	 * @param y
	 *            the y pixel-coordinate
	 * @return a Point whose components are in the range 0-NUM_LINES, indicating
	 *         the column and row of the corresponding intersection on the go
	 *         board
	 */
	public Point mapPixelToIntersection(int x, int y) {
		int xCoord = (int) (x / (spacing * currentWidth) - 0.5);
		int yCoord = (int) (y / (spacing * currentHeight) - 0.5);
		return new Point(xCoord, yCoord);
	}

	/**
	 * callback method--called when we are notified that the game is over
	 * 
	 * @param msg
	 * 		the "game over" message sent by the game
	 */
	@Override
	protected void gameIsOver(String msg) {
		// the default behavior is to put a pop-up for the user to see that tells
		// the game's result
		MessageBox.popUpMessage(msg, myActivity);
		
		passButton.setEnabled(false);
		commitButton.setEnabled(false);
		
		gameIsOver = true;
	}
	
	/*
	 * Converts a virtual (0-1) vertical position into a physical one that can
	 * be drawn.
	 */
	private int v(double relVertPos) {
		return (int) Math.round(relVertPos * currentHeight);

	}

	/*
	 * Converts a virtual (0-1) vertical position into a physical one that can
	 * be drawn.
	 */
	private int h(double relHorizPos) {
		return (int) Math.round(relHorizPos * currentWidth);

	}

	/**
	 * @param clearTmpStone
	 *            the tmpStoneSet to set
	 */
	public void clearTmpStone() {
		tmpStoneSet = false;
	}

}
